home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / lib / iceweasel / components / WebContentConverter.js < prev   
Encoding:
Text File  |  2013-01-09  |  30.6 KB  |  901 lines

  1. //@line 39 "/tmp/buildd/iceweasel-10.0.12esr/browser/components/feeds/src/WebContentConverter.js"
  2.  
  3. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  4.  
  5. const Cc = Components.classes;
  6. const Ci = Components.interfaces;
  7. const Cr = Components.results;
  8.  
  9. function LOG(str) {
  10.   dump("*** " + str + "\n");
  11. }
  12.  
  13. const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
  14. const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
  15.  
  16. const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
  17. const WCC_CLASSNAME = "Web Service Handler";
  18.  
  19. const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
  20. const TYPE_ANY = "*/*";
  21.  
  22. const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
  23. const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
  24. const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
  25. const PREF_SELECTED_ACTION = "browser.feeds.handler";
  26. const PREF_SELECTED_READER = "browser.feeds.handler.default";
  27. const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
  28. const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
  29.  
  30. const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
  31.  
  32. const NS_ERROR_MODULE_DOM = 2152923136;
  33. const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
  34.  
  35. function WebContentConverter() {
  36. }
  37. WebContentConverter.prototype = {
  38.   convert: function WCC_convert() { },
  39.   asyncConvertData: function WCC_asyncConvertData() { },
  40.   onDataAvailable: function WCC_onDataAvailable() { },
  41.   onStopRequest: function WCC_onStopRequest() { },
  42.   
  43.   onStartRequest: function WCC_onStartRequest(request, context) {
  44.     var wccr = 
  45.         Cc[WCCR_CONTRACTID].
  46.         getService(Ci.nsIWebContentConverterService);
  47.     wccr.loadPreferredHandler(request);
  48.   },
  49.   
  50.   QueryInterface: function WCC_QueryInterface(iid) {
  51.     if (iid.equals(Ci.nsIStreamConverter) ||
  52.         iid.equals(Ci.nsIStreamListener) ||
  53.         iid.equals(Ci.nsISupports))
  54.       return this;
  55.     throw Cr.NS_ERROR_NO_INTERFACE;
  56.   }
  57. };
  58.  
  59. var WebContentConverterFactory = {
  60.   createInstance: function WCCF_createInstance(outer, iid) {
  61.     if (outer != null)
  62.       throw Cr.NS_ERROR_NO_AGGREGATION;
  63.     return new WebContentConverter().QueryInterface(iid);
  64.   },
  65.     
  66.   QueryInterface: function WCC_QueryInterface(iid) {
  67.     if (iid.equals(Ci.nsIFactory) ||
  68.         iid.equals(Ci.nsISupports))
  69.       return this;
  70.     throw Cr.NS_ERROR_NO_INTERFACE;
  71.   }
  72. };
  73.  
  74. function ServiceInfo(contentType, uri, name) {
  75.   this._contentType = contentType;
  76.   this._uri = uri;
  77.   this._name = name;
  78. }
  79. ServiceInfo.prototype = {
  80.   /**
  81.    * See nsIHandlerApp
  82.    */
  83.   get name() {
  84.     return this._name;
  85.   },
  86.   
  87.   /**
  88.    * See nsIHandlerApp
  89.    */
  90.   equals: function SI_equals(aHandlerApp) {
  91.     if (!aHandlerApp)
  92.       throw Cr.NS_ERROR_NULL_POINTER;
  93.  
  94.     if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
  95.         aHandlerApp.contentType == this.contentType &&
  96.         aHandlerApp.uri == this.uri)
  97.       return true;
  98.  
  99.     return false;
  100.   },
  101.  
  102.   /**
  103.    * See nsIWebContentHandlerInfo
  104.    */
  105.   get contentType() {
  106.     return this._contentType;
  107.   },
  108.  
  109.   /**
  110.    * See nsIWebContentHandlerInfo
  111.    */
  112.   get uri() {
  113.     return this._uri;
  114.   },
  115.  
  116.   /**
  117.    * See nsIWebContentHandlerInfo
  118.    */
  119.   getHandlerURI: function SI_getHandlerURI(uri) {
  120.     return this._uri.replace(/%s/gi, encodeURIComponent(uri));
  121.   },
  122.   
  123.   QueryInterface: function SI_QueryInterface(iid) {
  124.     if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
  125.         iid.equals(Ci.nsISupports))
  126.       return this;
  127.     throw Cr.NS_ERROR_NO_INTERFACE;
  128.   }
  129. };
  130.  
  131. function WebContentConverterRegistrar() {
  132.   this._contentTypes = { };
  133.   this._autoHandleContentTypes = { };
  134. }
  135.  
  136. WebContentConverterRegistrar.prototype = {
  137.   get stringBundle() {
  138.     var sb = Cc["@mozilla.org/intl/stringbundle;1"].
  139.               getService(Ci.nsIStringBundleService).
  140.               createBundle(STRING_BUNDLE_URI);
  141.     delete WebContentConverterRegistrar.prototype.stringBundle;
  142.     return WebContentConverterRegistrar.prototype.stringBundle = sb;
  143.   },
  144.  
  145.   _getFormattedString: function WCCR__getFormattedString(key, params) {
  146.     return this.stringBundle.formatStringFromName(key, params, params.length);
  147.   },
  148.   
  149.   _getString: function WCCR_getString(key) {
  150.     return this.stringBundle.GetStringFromName(key);
  151.   },
  152.  
  153.   /**
  154.    * See nsIWebContentConverterService
  155.    */
  156.   getAutoHandler: 
  157.   function WCCR_getAutoHandler(contentType) {
  158.     contentType = this._resolveContentType(contentType);
  159.     if (contentType in this._autoHandleContentTypes)
  160.       return this._autoHandleContentTypes[contentType];
  161.     return null;
  162.   },
  163.   
  164.   /**
  165.    * See nsIWebContentConverterService
  166.    */
  167.   setAutoHandler:
  168.   function WCCR_setAutoHandler(contentType, handler) {
  169.     if (handler && !this._typeIsRegistered(contentType, handler.uri))
  170.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  171.       
  172.     contentType = this._resolveContentType(contentType);
  173.     this._setAutoHandler(contentType, handler);
  174.     
  175.     var ps = 
  176.         Cc["@mozilla.org/preferences-service;1"].
  177.         getService(Ci.nsIPrefService);
  178.     var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
  179.     if (handler)
  180.       autoBranch.setCharPref(contentType, handler.uri);
  181.     else if (autoBranch.prefHasUserValue(contentType))
  182.       autoBranch.clearUserPref(contentType);
  183.      
  184.     ps.savePrefFile(null);
  185.   },
  186.   
  187.   /**
  188.    * Update the internal data structure (not persistent)
  189.    */
  190.   _setAutoHandler:
  191.   function WCCR__setAutoHandler(contentType, handler) {
  192.     if (handler) 
  193.       this._autoHandleContentTypes[contentType] = handler;
  194.     else if (contentType in this._autoHandleContentTypes)
  195.       delete this._autoHandleContentTypes[contentType];
  196.   },
  197.   
  198.   /**
  199.    * See nsIWebContentConverterService
  200.    */
  201.   getWebContentHandlerByURI:
  202.   function WCCR_getWebContentHandlerByURI(contentType, uri) {
  203.     var handlers = this.getContentHandlers(contentType, { });
  204.     for (var i = 0; i < handlers.length; ++i) {
  205.       if (handlers[i].uri == uri) 
  206.         return handlers[i];
  207.     }
  208.     return null;
  209.   },
  210.   
  211.   /**
  212.    * See nsIWebContentConverterService
  213.    */
  214.   loadPreferredHandler: 
  215.   function WCCR_loadPreferredHandler(request) {
  216.     var channel = request.QueryInterface(Ci.nsIChannel);
  217.     var contentType = this._resolveContentType(channel.contentType);
  218.     var handler = this.getAutoHandler(contentType);
  219.     if (handler) {
  220.       request.cancel(Cr.NS_ERROR_FAILURE);
  221.       
  222.       var webNavigation = 
  223.           channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
  224.       webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec), 
  225.                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE, 
  226.                             null, null, null);
  227.     }      
  228.   },
  229.   
  230.   /**
  231.    * See nsIWebContentConverterService
  232.    */
  233.   removeProtocolHandler: 
  234.   function WCCR_removeProtocolHandler(aProtocol, aURITemplate) {
  235.     var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
  236.               getService(Ci.nsIExternalProtocolService);
  237.     var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
  238.     var handlers =  handlerInfo.possibleApplicationHandlers;
  239.     for (let i = 0; i < handlers.length; i++) {
  240.       try { // We only want to test web handlers
  241.         let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
  242.         if (handler.uriTemplate == aURITemplate) {
  243.           handlers.removeElementAt(i);
  244.           var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
  245.                    getService(Ci.nsIHandlerService);
  246.           hs.store(handlerInfo);
  247.           return;
  248.         }
  249.       } catch (e) { /* it wasn't a web handler */ }
  250.     }
  251.   },
  252.   
  253.   /**
  254.    * See nsIWebContentConverterService
  255.    */
  256.   removeContentHandler: 
  257.   function WCCR_removeContentHandler(contentType, uri) {
  258.     function notURI(serviceInfo) {
  259.       return serviceInfo.uri != uri;
  260.     }
  261.   
  262.     if (contentType in this._contentTypes) {
  263.       this._contentTypes[contentType] = 
  264.         this._contentTypes[contentType].filter(notURI);
  265.     }
  266.   },
  267.   
  268.   /**
  269.    *
  270.    */
  271.   _mappings: { 
  272.     "application/rss+xml": TYPE_MAYBE_FEED,
  273.     "application/atom+xml": TYPE_MAYBE_FEED,
  274.   },
  275.   
  276.   /**
  277.    * These are types for which there is a separate content converter aside 
  278.    * from our built in generic one. We should not automatically register
  279.    * a factory for creating a converter for these types.
  280.    */
  281.   _blockedTypes: {
  282.     "application/vnd.mozilla.maybe.feed": true,
  283.   },
  284.   
  285.   /**
  286.    * Determines the "internal" content type based on the _mappings.
  287.    * @param   contentType
  288.    * @returns The resolved contentType value. 
  289.    */
  290.   _resolveContentType: 
  291.   function WCCR__resolveContentType(contentType) {
  292.     if (contentType in this._mappings)
  293.       return this._mappings[contentType];
  294.     return contentType;
  295.   },
  296.  
  297.   _makeURI: function(aURL, aOriginCharset, aBaseURI) {
  298.     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  299.                               .getService(Components.interfaces.nsIIOService);
  300.     return ioService.newURI(aURL, aOriginCharset, aBaseURI);
  301.   },
  302.  
  303.   _checkAndGetURI:
  304.   function WCCR_checkAndGetURI(aURIString, aContentWindow)
  305.   {
  306.     try {
  307.       var uri = this._makeURI(aURIString);
  308.     } catch (ex) {
  309.       // not supposed to throw according to spec
  310.       return; 
  311.     }
  312.  
  313.     // For security reasons we reject non-http(s) urls (see bug 354316),
  314.     // we may need to revise this once we support more content types
  315.     // XXX this should be a "security exception" according to spec, but that
  316.     // isn't defined yet.
  317.     if (uri.scheme != "http" && uri.scheme != "https")
  318.       throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
  319.  
  320.     // We also reject handlers registered from a different host (see bug 402287)
  321.     // The pref allows us to test the feature
  322.     var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  323.     if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) ||
  324.          !pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) &&
  325.         aContentWindow.location.hostname != uri.host)
  326.       throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
  327.  
  328.     // If the uri doesn't contain '%s', it won't be a good handler
  329.     if (uri.spec.indexOf("%s") < 0)
  330.       throw NS_ERROR_DOM_SYNTAX_ERR; 
  331.  
  332.     return uri;
  333.   },
  334.  
  335.   /**
  336.    * Determines if a web handler is already registered.
  337.    *
  338.    * @param aProtocol
  339.    *        The scheme of the web handler we are checking for.
  340.    * @param aURITemplate
  341.    *        The URI template that the handler uses to handle the protocol.
  342.    * @return true if it is already registered, false otherwise.
  343.    */
  344.   _protocolHandlerRegistered:
  345.   function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) {
  346.     var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
  347.               getService(Ci.nsIExternalProtocolService);
  348.     var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
  349.     var handlers =  handlerInfo.possibleApplicationHandlers;
  350.     for (let i = 0; i < handlers.length; i++) {
  351.       try { // We only want to test web handlers
  352.         let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
  353.         if (handler.uriTemplate == aURITemplate)
  354.           return true;
  355.       } catch (e) { /* it wasn't a web handler */ }
  356.     }
  357.     return false;
  358.   },
  359.  
  360.   /**
  361.    * See nsIWebContentHandlerRegistrar
  362.    */
  363.   registerProtocolHandler: 
  364.   function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) {
  365.     LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
  366.  
  367.     if (Cc["@mozilla.org/privatebrowsing;1"].
  368.         getService(Ci.nsIPrivateBrowsingService).
  369.         privateBrowsingEnabled) {
  370.       // Inside the private browsing mode, we don't want to alert the user to save
  371.       // a protocol handler.  We log it to the error console so that web developers
  372.       // would have some way to tell what's going wrong.
  373.       Cc["@mozilla.org/consoleservice;1"].
  374.       getService(Ci.nsIConsoleService).
  375.       logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
  376.       return;
  377.     }
  378.     
  379.     // First, check to make sure this isn't already handled internally (we don't
  380.     // want to let them take over, say "chrome").
  381.     var ios = Cc["@mozilla.org/network/io-service;1"].
  382.               getService(Ci.nsIIOService);
  383.     var handler = ios.getProtocolHandler(aProtocol);
  384.     if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
  385.       // This is handled internally, so we don't want them to register
  386.       // XXX this should be a "security exception" according to spec, but that
  387.       // isn't defined yet.
  388.       throw("Permission denied to add " + aURIString + "as a protocol handler");
  389.     }
  390.  
  391.     // check if it is in the black list
  392.     var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  393.     var allowed;
  394.     try {
  395.       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
  396.     }
  397.     catch (e) {
  398.       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
  399.     }
  400.     if (!allowed) {
  401.       // XXX this should be a "security exception" according to spec
  402.       throw("Not allowed to register a protocol handler for " + aProtocol);
  403.     }
  404.  
  405.     var uri = this._checkAndGetURI(aURIString, aContentWindow);
  406.  
  407.     var buttons, message;
  408.     if (this._protocolHandlerRegistered(aProtocol, uri.spec))
  409.       message = this._getFormattedString("protocolHandlerRegistered",
  410.                                          [aTitle, aProtocol]);
  411.     else {
  412.       // Now Ask the user and provide the proper callback
  413.       message = this._getFormattedString("addProtocolHandler",
  414.                                          [aTitle, uri.host, aProtocol]);
  415.       var fis = Cc["@mozilla.org/browser/favicon-service;1"].
  416.                 getService(Ci.nsIFaviconService);
  417.       var notificationIcon = fis.getFaviconLinkForIcon(uri);
  418.       var notificationValue = "Protocol Registration: " + aProtocol;
  419.       var addButton = {
  420.         label: this._getString("addProtocolHandlerAddButton"),
  421.         accessKey: this._getString("addHandlerAddButtonAccesskey"),
  422.         protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
  423.  
  424.         callback:
  425.         function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) {
  426.           var protocol = aButtonInfo.protocolInfo.protocol;
  427.           var uri      = aButtonInfo.protocolInfo.uri;
  428.           var name     = aButtonInfo.protocolInfo.name;
  429.  
  430.           var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
  431.                         createInstance(Ci.nsIWebHandlerApp);
  432.           handler.name = name;
  433.           handler.uriTemplate = uri;
  434.  
  435.           var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
  436.                     getService(Ci.nsIExternalProtocolService);
  437.           var handlerInfo = eps.getProtocolHandlerInfo(protocol);
  438.           handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
  439.  
  440.           // Since the user has agreed to add a new handler, chances are good
  441.           // that the next time they see a handler of this type, they're going
  442.           // to want to use it.  Reset the handlerInfo to ask before the next
  443.           // use.
  444.           handlerInfo.alwaysAskBeforeHandling = true;
  445.  
  446.           var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
  447.                    getService(Ci.nsIHandlerService);
  448.           hs.store(handlerInfo);
  449.         }
  450.       };
  451.       buttons = [addButton];
  452.     }
  453.  
  454.     var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
  455.     var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
  456.     var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
  457.     notificationBox.appendNotification(message,
  458.                                        notificationValue,
  459.                                        notificationIcon,
  460.                                        notificationBox.PRIORITY_INFO_LOW,
  461.                                        buttons);
  462.   },
  463.  
  464.   /**
  465.    * See nsIWebContentHandlerRegistrar
  466.    * If a DOM window is provided, then the request came from content, so we
  467.    * prompt the user to confirm the registration.
  468.    */
  469.   registerContentHandler: 
  470.   function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) {
  471.     LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
  472.  
  473.     // We only support feed types at present.
  474.     // XXX this should be a "security exception" according to spec, but that
  475.     // isn't defined yet.
  476.     var contentType = this._resolveContentType(aContentType);
  477.     if (contentType != TYPE_MAYBE_FEED)
  478.       return;
  479.  
  480.     if (aContentWindow) {
  481.       var uri = this._checkAndGetURI(aURIString, aContentWindow);
  482.   
  483.       var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
  484.       var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
  485.       var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
  486.       this._appendFeedReaderNotification(uri, aTitle, notificationBox);
  487.     }
  488.     else
  489.       this._registerContentHandler(contentType, aURIString, aTitle);
  490.   },
  491.  
  492.   /**
  493.    * Returns the browser chrome window in which the content window is in
  494.    */
  495.   _getBrowserWindowForContentWindow:
  496.   function WCCR__getBrowserWindowForContentWindow(aContentWindow) {
  497.     return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  498.                          .getInterface(Ci.nsIWebNavigation)
  499.                          .QueryInterface(Ci.nsIDocShellTreeItem)
  500.                          .rootTreeItem
  501.                          .QueryInterface(Ci.nsIInterfaceRequestor)
  502.                          .getInterface(Ci.nsIDOMWindow)
  503.                          .wrappedJSObject;
  504.   },
  505.  
  506.   /**
  507.    * Returns the <xul:browser> element associated with the given content
  508.    * window.
  509.    *
  510.    * @param aBrowserWindow
  511.    *        The browser window in which the content window is in.
  512.    * @param aContentWindow
  513.    *        The content window. It's possible to pass a child content window
  514.    *        (i.e. the content window of a frame/iframe).
  515.    */
  516.   _getBrowserForContentWindow:
  517.   function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
  518.     // This depends on pseudo APIs of browser.js and tabbrowser.xml
  519.     aContentWindow = aContentWindow.top;
  520.     var browsers = aBrowserWindow.getBrowser().browsers;
  521.     for (var i = 0; i < browsers.length; ++i) {
  522.       if (browsers[i].contentWindow == aContentWindow)
  523.         return browsers[i];
  524.     }
  525.   },
  526.  
  527.   /**
  528.    * Appends a notifcation for the given feed reader details.
  529.    *
  530.    * The notification could be either a pseudo-dialog which lets
  531.    * the user to add the feed reader:
  532.    * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader?  (Add) [x] ]
  533.    *
  534.    * or a simple message for the case where the feed reader is already registered:
  535.    * [ [icon] %feed-reader-name% is already registered as a Feed Reader             [x] ]
  536.    *
  537.    * A new notification isn't appended if the given notificationbox has a
  538.    * notification for the same feed reader.
  539.    *
  540.    * @param aURI
  541.    *        The url of the feed reader as a nsIURI object
  542.    * @param aName
  543.    *        The feed reader name as it was passed to registerContentHandler
  544.    * @param aNotificationBox
  545.    *        The notification box to which a notification might be appended
  546.    * @return true if a notification has been appended, false otherwise.
  547.    */
  548.   _appendFeedReaderNotification:
  549.   function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) {
  550.     var uriSpec = aURI.spec;
  551.     var notificationValue = "feed reader notification: " + uriSpec;
  552.     var notificationIcon = aURI.prePath + "/favicon.ico";
  553.  
  554.     // Don't append a new notification if the notificationbox
  555.     // has a notification for the given feed reader already
  556.     if (aNotificationBox.getNotificationWithValue(notificationValue))
  557.       return false;
  558.  
  559.     var buttons, message;
  560.     if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
  561.       message = this._getFormattedString("handlerRegistered", [aName]);
  562.     else {
  563.       message = this._getFormattedString("addHandler", [aName, aURI.host]);
  564.       var self = this;
  565.       var addButton = {
  566.         _outer: self,
  567.         label: self._getString("addHandlerAddButton"),
  568.         accessKey: self._getString("addHandlerAddButtonAccesskey"),
  569.         feedReaderInfo: { uri: uriSpec, name: aName },
  570.  
  571.         /* static */
  572.         callback:
  573.         function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) {
  574.           var uri = aButtonInfo.feedReaderInfo.uri;
  575.           var name = aButtonInfo.feedReaderInfo.name;
  576.           var outer = aButtonInfo._outer;
  577.  
  578.           // The reader could have been added from another window mean while
  579.           if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
  580.             outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
  581.  
  582.           // avoid reference cycles
  583.           aButtonInfo._outer = null;
  584.  
  585.           return false;
  586.         }
  587.       };
  588.       buttons = [addButton];
  589.     }
  590.  
  591.     aNotificationBox.appendNotification(message,
  592.                                         notificationValue,
  593.                                         notificationIcon,
  594.                                         aNotificationBox.PRIORITY_INFO_LOW,
  595.                                         buttons);
  596.     return true;
  597.   },
  598.  
  599.   /**
  600.    * Save Web Content Handler metadata to persistent preferences. 
  601.    * @param   contentType
  602.    *          The content Type being handled
  603.    * @param   uri
  604.    *          The uri of the web service
  605.    * @param   title
  606.    *          The human readable name of the web service
  607.    *
  608.    * This data is stored under:
  609.    * 
  610.    *    browser.contentHandlers.type0 = content/type
  611.    *    browser.contentHandlers.uri0 = http://www.foo.com/q=%s
  612.    *    browser.contentHandlers.title0 = Foo 2.0alphr
  613.    */
  614.   _saveContentHandlerToPrefs: 
  615.   function WCCR__saveContentHandlerToPrefs(contentType, uri, title) {
  616.     var ps = 
  617.         Cc["@mozilla.org/preferences-service;1"].
  618.         getService(Ci.nsIPrefService);
  619.     var i = 0;
  620.     var typeBranch = null;
  621.     while (true) {
  622.       typeBranch = 
  623.         ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
  624.       try {
  625.         typeBranch.getCharPref("type");
  626.         ++i;
  627.       }
  628.       catch (e) {
  629.         // No more handlers
  630.         break;
  631.       }
  632.     }
  633.     if (typeBranch) {
  634.       typeBranch.setCharPref("type", contentType);
  635.       var pls = 
  636.           Cc["@mozilla.org/pref-localizedstring;1"].
  637.           createInstance(Ci.nsIPrefLocalizedString);
  638.       pls.data = uri;
  639.       typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
  640.       pls.data = title;
  641.       typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
  642.     
  643.       ps.savePrefFile(null);
  644.     }
  645.   },
  646.   
  647.   /**
  648.    * Determines if there is a type with a particular uri registered for the 
  649.    * specified content type already.
  650.    * @param   contentType
  651.    *          The content type that the uri handles
  652.    * @param   uri
  653.    *          The uri of the 
  654.    */
  655.   _typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) {
  656.     if (!(contentType in this._contentTypes))
  657.       return false;
  658.       
  659.     var services = this._contentTypes[contentType];
  660.     for (var i = 0; i < services.length; ++i) {
  661.       // This uri has already been registered
  662.       if (services[i].uri == uri)
  663.         return true;
  664.     }
  665.     return false;
  666.   },
  667.   
  668.   /**
  669.    * Gets a stream converter contract id for the specified content type.
  670.    * @param   contentType
  671.    *          The source content type for the conversion.
  672.    * @returns A contract id to construct a converter to convert between the 
  673.    *          contentType and *\/*.
  674.    */
  675.   _getConverterContractID: function WCCR__getConverterContractID(contentType) {
  676.     const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
  677.     return template.replace(/%s/, contentType);
  678.   },
  679.   
  680.   /**
  681.    * Register a web service handler for a content type.
  682.    * 
  683.    * @param   contentType
  684.    *          the content type being handled
  685.    * @param   uri
  686.    *          the URI of the web service
  687.    * @param   title
  688.    *          the human readable name of the web service
  689.    */
  690.   _registerContentHandler:
  691.   function WCCR__registerContentHandler(contentType, uri, title) {
  692.     this._updateContentTypeHandlerMap(contentType, uri, title);
  693.     this._saveContentHandlerToPrefs(contentType, uri, title);
  694.  
  695.     if (contentType == TYPE_MAYBE_FEED) {
  696.       // Make the new handler the last-selected reader in the preview page
  697.       // and make sure the preview page is shown the next time a feed is visited
  698.       var pb = Cc["@mozilla.org/preferences-service;1"].
  699.                getService(Ci.nsIPrefService).getBranch(null);
  700.       pb.setCharPref(PREF_SELECTED_READER, "web");
  701.   
  702.       var supportsString = 
  703.         Cc["@mozilla.org/supports-string;1"].
  704.         createInstance(Ci.nsISupportsString);
  705.         supportsString.data = uri;
  706.       pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
  707.                          supportsString);
  708.       pb.setCharPref(PREF_SELECTED_ACTION, "ask");
  709.       this._setAutoHandler(TYPE_MAYBE_FEED, null);
  710.     }
  711.   },
  712.  
  713.   /**
  714.    * Update the content type -> handler map. This mapping is not persisted, use
  715.    * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
  716.    * @param   contentType
  717.    *          The content Type being handled
  718.    * @param   uri
  719.    *          The uri of the web service
  720.    * @param   title
  721.    *          The human readable name of the web service
  722.    */
  723.   _updateContentTypeHandlerMap: 
  724.   function WCCR__updateContentTypeHandlerMap(contentType, uri, title) {
  725.     if (!(contentType in this._contentTypes))
  726.       this._contentTypes[contentType] = [];
  727.  
  728.     // Avoid adding duplicates
  729.     if (this._typeIsRegistered(contentType, uri)) 
  730.       return;
  731.     
  732.     this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
  733.     
  734.     if (!(contentType in this._blockedTypes)) {
  735.       var converterContractID = this._getConverterContractID(contentType);
  736.       var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
  737.       cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID, 
  738.                          WebContentConverterFactory);
  739.     }
  740.   },
  741.   
  742.   /**
  743.    * See nsIWebContentConverterService
  744.    */
  745.   getContentHandlers: 
  746.   function WCCR_getContentHandlers(contentType, countRef) {
  747.     countRef.value = 0;
  748.     if (!(contentType in this._contentTypes))
  749.       return [];
  750.     
  751.     var handlers = this._contentTypes[contentType];
  752.     countRef.value = handlers.length;
  753.     return handlers;
  754.   },
  755.   
  756.   /**
  757.    * See nsIWebContentConverterService
  758.    */
  759.   resetHandlersForType: 
  760.   function WCCR_resetHandlersForType(contentType) {
  761.     // currently unused within the tree, so only useful for extensions; previous
  762.     // impl. was buggy (and even infinite-looped!), so I argue that this is a
  763.     // definite improvement
  764.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  765.   },
  766.   
  767.   /**
  768.    * Registers a handler from the settings on a preferences branch.
  769.    *
  770.    * @param branch
  771.    *        an nsIPrefBranch containing "type", "uri", and "title" preferences
  772.    *        corresponding to the content handler to be registered
  773.    */
  774.   _registerContentHandlerWithBranch: function(branch) {
  775.     /**
  776.      * Since we support up to six predefined readers, we need to handle gaps 
  777.      * better, since the first branch with user-added values will be .6
  778.      * 
  779.      * How we deal with that is to check to see if there's no prefs in the 
  780.      * branch and stop cycling once that's true.  This doesn't fix the case
  781.      * where a user manually removes a reader, but that's not supported yet!
  782.      */
  783.     var vals = branch.getChildList("");
  784.     if (vals.length == 0)
  785.       return;
  786.  
  787.     try {
  788.       var type = branch.getCharPref("type");
  789.       var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
  790.       var title = branch.getComplexValue("title",
  791.                                          Ci.nsIPrefLocalizedString).data;
  792.       this._updateContentTypeHandlerMap(type, uri, title);
  793.     }
  794.     catch(ex) {
  795.       // do nothing, the next branch might have values
  796.     }
  797.   },
  798.  
  799.   /**
  800.    * Load the auto handler, content handler and protocol tables from 
  801.    * preferences.
  802.    */
  803.   _init: function WCCR__init() {
  804.     var ps = 
  805.         Cc["@mozilla.org/preferences-service;1"].
  806.         getService(Ci.nsIPrefService);
  807.  
  808.     var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
  809.                  .getChildList("");
  810.  
  811.     // first get the numbers of the providers by getting all ###.uri prefs
  812.     var nums = [];
  813.     for (var i = 0; i < kids.length; i++) {
  814.       var match = /^(\d+)\.uri$/.exec(kids[i]);
  815.       if (!match)
  816.         continue;
  817.       else
  818.         nums.push(match[1]);
  819.     }
  820.  
  821.     // sort them, to get them back in order
  822.     nums.sort(function(a, b) {return a - b;});
  823.  
  824.     // now register them
  825.     for (var i = 0; i < nums.length; i++) {
  826.       var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
  827.       this._registerContentHandlerWithBranch(branch);
  828.     }
  829.  
  830.     // We need to do this _after_ registering all of the available handlers, 
  831.     // so that getWebContentHandlerByURI can return successfully.
  832.     try {
  833.       var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
  834.       var childPrefs = autoBranch.getChildList("");
  835.       for (var i = 0; i < childPrefs.length; ++i) {
  836.         var type = childPrefs[i];
  837.         var uri = autoBranch.getCharPref(type);
  838.         if (uri) {
  839.           var handler = this.getWebContentHandlerByURI(type, uri);
  840.           this._setAutoHandler(type, handler);
  841.         }
  842.       }
  843.     }
  844.     catch (e) {
  845.       // No auto branch yet, that's fine
  846.       //LOG("WCCR.init: There is no auto branch, benign");
  847.     }
  848.   },
  849.  
  850.   /**
  851.    * See nsIObserver
  852.    */
  853.   observe: function WCCR_observe(subject, topic, data) {
  854.     var os = 
  855.         Cc["@mozilla.org/observer-service;1"].
  856.         getService(Ci.nsIObserverService);
  857.     switch (topic) {
  858.     case "app-startup":
  859.       os.addObserver(this, "browser-ui-startup-complete", false);
  860.       break;
  861.     case "browser-ui-startup-complete":
  862.       os.removeObserver(this, "browser-ui-startup-complete");
  863.       this._init();
  864.       break;
  865.     }
  866.   },
  867.   
  868.   /**
  869.    * See nsIFactory
  870.    */
  871.   createInstance: function WCCR_createInstance(outer, iid) {
  872.     if (outer != null)
  873.       throw Cr.NS_ERROR_NO_AGGREGATION;
  874.     return this.QueryInterface(iid);
  875.   },
  876.  
  877.   classID: WCCR_CLASSID,
  878.   classInfo: XPCOMUtils.generateCI({classID: WCCR_CLASSID,
  879.                                     contractID: WCCR_CONTRACTID,
  880.                                     interfaces: [Ci.nsIWebContentConverterService,
  881.                                                  Ci.nsIWebContentHandlerRegistrar,
  882.                                                  Ci.nsIObserver, Ci.nsIFactory],
  883.                                     flags: Ci.nsIClassInfo.DOM_OBJECT}),
  884.  
  885.   /**
  886.    * See nsISupports
  887.    */
  888.   QueryInterface: XPCOMUtils.generateQI(
  889.      [Ci.nsIWebContentConverterService, 
  890.       Ci.nsIWebContentHandlerRegistrar,
  891.       Ci.nsIObserver,
  892.       Ci.nsIFactory]),
  893.  
  894.   _xpcom_categories: [{
  895.     category: "app-startup",
  896.     service: true
  897.   }]
  898. };
  899.  
  900. var NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);
  901.